#! /usr/bin/env bash
#########################################
# GUI for x11docker 
# Published under MIT licence
# Uses 'kaptain' to provide a GUI
# https://github.com/mviereck/x11docker
#########################################

error() {
  echo $1 >&2
  command -v xmessage >/dev/null && {
    xmessage -default okay "$1" ||:
    :
  } || { 
    command -v zenity >/dev/null && {
      zenity --error --ellipsize --text="$1" ||:
    }
  } || notify-send "$1"
  exit 1
}
warning() {
  echo "x11docker-gui: $*
" >&2
}
verbose() {
  [ "$Verbose" = "yes" ] && echo "$(tput setaf 4)x11docker-gui:$(tput sgr0) $*" >&2
}
finish() {
  # clean up and say goodbye
  [ -e "$Timetosaygoodbye" ] && echo timetosaygoodbye >> $Timetosaygoodbye
  for Line in $Kaptain_grammar_file $Kaptain_stdin $Kaptain_stdout ; do
    [ -e "$Line" ] && rm "$Line"
  done
  [ "$Kaptain_pid" ] && ps -p $Kaptain_pid >/dev/null 2>/dev/null && kill $Kaptain_pid
  exit 0
}
mkfile() {              # create file $1 owned by $Hostuser
  :> "${1:-}"
  chown $Lognameuser    "${1:-}"
  chgrp $Lognameusergid "${1:-}"
  [ -n "${2:-}" ] && chmod ${2:-} "${1:-}" ||:
}

trap finish EXIT
export PATH="$PATH:/usr/local/bin" # can miss if executed as root

# check options
#Verboseoption="--quiet"
Verboseoption=""
case "${1:-}" in
  --icon)
    echo "x11docker-gui does not create an icon on demand anymore." >&2
    exit 1
  ;;
  -v|--verbose) 
    Verbose="yes"
    Verboseoption="--verbose" 
  ;;
  "") ;;
  *) echo "Options:
  -v  --verbose    Be verbose"
    exit 1
  ;;
esac

# Try to find x11docker
X11docker_exe="${0%-gui}"                                   # look at path of current x11docker-gui
command -v x11docker >/dev/null && X11docker_exe=x11docker  # look whether x11docker is in $PATH
[ ! -x "$X11docker_exe" ] && ! command -v x11docker >/dev/null && error "No executeable x11docker found"
Version="$($X11docker_exe --version)"
verbose "Found x11docker: $X11docker_exe $Version"

# check logged-in user
Lognameuser="$(logname 2>/dev/null)"
[ -z "$Lognameuser" ] && warning "Your terminal seems to be not POSIX compliant.
  Command 'logname' does not return a value.
  Consider to use another terminal emulator."
[ -z "$Lognameuser" ] && [ -n "$SUDO_USER" ]  && Lognameuser="$SUDO_USER"  && warning "Will assume \$SUDO_USER = $SUDO_USER as host user."
[ -z "$Lognameuser" ] && [ -n "$PKEXEC_UID" ] && Lognameuser="$PKEXEC_UID" && warning "Will assume user with uid \$PKEXEC_UID = $PKEXEC_UID as host user."
[ -z "$Lognameuser" ] &&                         Lognameuser="$(id -un)"   && warning "Will assume \$(id -un) = $(id -un) as host user."
Lognameusergid="$(getent passwd $Lognameuser | cut -d: -f4)"
Lognameuserhome="$(getent passwd $Lognameuser | cut -d: -f6)"

Cachefolder=$Lognameuserhome/.cache/x11docker/x11docker-gui
mkdir -p $Lognameuserhome/.cache/x11docker
chown $Lognameuser $Lognameuserhome/.cache/x11docker
mkdir -p $Cachefolder || Cachefolder=/tmp/x11docker-gui
mkdir -p $Cachefolder 
chown $Lognameuser $Cachefolder 
Mycookie=$(mcookie | cut -c1-6)
Kaptain_grammar_file=$Cachefolder/x11docker.kaptn.$Mycookie
mkfile $Kaptain_grammar_file


Containeruser="$Lognameuser"
Containeruserhome="$HOME"
## needed for github screenshots only
#Containeruser="\\\$USER"
#Containeruserhome="/home/\\\$USER"


# check sound cards for ALSA
Alsalist=""
while read Line; do
  Alsalist="\"$Line\",$Alsalist"
done < <(cat /proc/asound/cards | grep ':' | cut -d[ -f2 | cut -d] -f1)
Alsalist="$Alsalist\"\""
[ "$ALSA_CARD" ] && Alsalist="\"$ALSA_CARD\",$Alsalist"

# check image list stored by x11docker
Imagelist='""'
[ -e "$Cachefolder/../docker.imagelist" ] && while read -r Line ; do
  Line="${Line%:latest}"
  Imagelist="$Imagelist,\"$Line \""
done < "$Cachefolder/../docker.imagelist"
Imagelist="$Imagelist,"'"",
"x11docker/check",
"x11docker/xfce ",
"x11docker/lxde ",
"x11docker/lxqt ",
"x11docker/mate ",
"x11docker/fluxbox ",
"x11docker/fvwm ",
"x11docker/lumina ",
"x11docker/trinity ",
"x11docker/kde-plasma ",
"x11docker/enlightenment ",
"x11docker/lxde-wine ",
"x11docker/xfce-wine-playonlinux ",
"x11docker/cinnamon \ \ # (needs --init=systemd)",
"x11docker/xwayland \ \ # (needs --wayland and --gpu)"'

# create combo of language locales
Hostlangland=$(echo $LANG | cut -d. -f1)
Langcombo="\"$Hostlangland\""
for Line in $(echo "aa_DJ aa_ER aa_ET af_ZA ak_GH am_ET an_ES anp_IN 
ar_AE ar_BH ar_DZ ar_EG ar_IN ar_IQ ar_JO ar_KW ar_LB ar_LY ar_MA ar_OM ar_QA ar_SA ar_SD ar_SS ar_SY ar_TN ar_YE as_IN ast_ES ayc_PE az_AZ
be_BY bem_ZM ber_DZ ber_MA bg_BG bhb_IN bho_IN bn_BD bn_IN bo_CN bo_IN br_FR brx_IN bs_BA byn_ER C 
ca_AD ca_ES ca_FR ca_IT ce_RU chr_US cmn_TW crh_UA csb_PL cs_CZ cv_RU cy_GB da_DK de_AT de_BE de_CH de_DE de_IT de_LI de_LU doi_IN dv_MV dz_BT 
el_CY el_GR en_AG en_AU en_BW en_CA en_DK en_GB en_HK en_IE en_IL en_IN en_NG en_NZ en_PH en_SG en_US en_ZA en_ZM en_ZW eo 
es_AR es_BO es_CL es_CO es_CR es_CU es_DO es_EC es_ES es_GT es_HN es_MX es_NI es_PA es_PE es_PR es_PY es_SV es_US es_UY es_VE et_EE eu_ES eu_FR 
fa_IR ff_SN fi_FI fil_PH fo_FO fr_BE fr_CA fr_CH fr_FR fr_LU fur_IT fy_DE fy_NL ga_IE gd_GB gez_ER gez_ET gl_ES gu_IN gv_GB 
hak_TW ha_NG he_IL hi_IN hne_IN hr_HR hsb_DE ht_HT hu_HU hy_AM ia_FR id_ID ig_NG ik_CA is_IS it_CH it_IT iu_CA 
ja_JP ka_GE kk_KZ kl_GL km_KH kn_IN kok_IN ko_KR ks_IN ku_TR kw_GB ky_KG lb_LU lg_UG li_BE lij_IT li_NL ln_CD lo_LA lt_LT lv_LV lzh_TW 
mag_IN mai_IN mg_MG mhr_RU mi_NZ mk_MK ml_IN mni_IN mn_MN mr_IN ms_MY mt_MT my_MM 
nan_TW nb_NO nds_DE nds_NL ne_NP nhn_MX niu_NU niu_NZ nl_AW nl_BE nl_NL nn_NO nr_ZA nso_ZA 
oc_FR om_ET om_KE or_IN os_RU pa_IN pap_AW pap_CW pa_PK pl_PL POSIX ps_AF pt_BR pt_PT quz_PE raj_IN ro_RO ru_RU ru_UA rw_RW 
sa_IN sat_IN sc_IT sd_IN se_NO sgs_LT shs_CA sid_ET si_LK sk_SK sl_SI so_DJ so_ET so_KE so_SO sq_AL sq_MK sr_ME sr_RS ss_ZA st_ZA sv_FI sv_SE sw_KE sw_TZ szl_PL 
ta_IN ta_LK tcy_IN te_IN tg_TJ the_NP th_TH ti_ER ti_ET tig_ER tk_TM tl_PH tn_ZA tr_CY tr_TR ts_ZA tt_RU 
ug_CN uk_UA unm_US ur_IN ur_PK uz_UZ ve_ZA vi_VN wa_BE wae_CH wal_ET wo_SN xh_ZA yi_US yo_NG yue_HK zh_CN zh_HK zh_SG zh_TW zu_ZA")
do Langcombo="$Langcombo,\"$Line\""; done

# create combo of keymaps
Keymaps="$(sed '/^! layout$/,/^ *$/!d ; //d' /usr/share/X11/xkb/rules/base.lst 2>/dev/null | awk '{print $1}' | sort)"
Hostkeymap="$(setxkbmap -query | grep layout | rev | cut -d' ' -f1 | rev)"
Keymapscombo=\"$Hostkeymap\"
Keymapscombo=${Keymapscombo:-\"\"}
for Line in $Keymaps; do Keymapscombo="$Keymapscombo,\"$Line\"" ; done

# --preset
Presetcombo=""
while read Line; do
  Presetcombo="$Presetcombo,'$Line'"
done < <(find /etc/x11docker/preset $HOME/.config/x11docker/preset -type f -exec basename {} \; | sort -V | uniq)
Presetcombo="${Presetcombo#,}"
Presetcombo="${Presetcombo:-"''"}"

# Look for icon
[ -e /usr/share/icons/hicolor/64x64/apps/x11docker.png ]       && X11docker_icon="/usr/share/icons/hicolor/64x64/apps/x11docker.png"
[ -e /usr/local/share/icons/hicolor/64x64/apps/x11docker.png ] && X11docker_icon="/usr/local/share/icons/hicolor/64x64/apps/x11docker.png"

Kaptain_grammar='#! /usr/bin/kaptain
start "Run GUI applications in containers - x11docker '$Version'"    -> options previewline buttons;
  options:beside                       -> frameleft frameright;
    frameleft                          -> choose_image_framed xserver_tabbed;
    frameright                         -> options_tabbed;
    
  choose_image_framed:framed           -> choose_image;
  choose_image "Choose image"          -> logo_imageorexe dockerimage backend;
    logo_imageorexe:beside             -> @icon("'$X11docker_icon'") imageorexe ;
      imageorexe                       -> image | exe | xonly;
        image:beside      "Run container from image."                                        -> "-- " ;
        exe               "--exe: Run host application instead of docker container."         -> "--exe -- ";
        xonly             "--xonly: Only create new empty X server."                         -> "--xonly -- "; 
    dockerimage           "Image [and command] to run:"                                      -> "--IMG=" @combow('$Imagelist');
    backend:beside "--backend:"        -> backenddocker | backendpodman | backendnerdctl;
      backenddocker "docker"             -> "" ;
      backendpodman "podman"             -> "--backend=podman" ;
      backendnerdctl "nerdctl"           -> "--backend=nerdctl" ;
  
  xserver_tabbed:tabbed                                             -> xserver_choose xserver_appearance;
  
  xserver_choose "Choose X server"                                  -> xservercombo desktop gpu wayland wm @line fullscreen size ;
    xservercombo -> s/(:.*$)/ / @combo(
        ": Automatical choice of X server",
        "--xpra:             Seamless application windows",
        "--xephyr:           Desktop window",
        "--nxagent:          Seamless applications and --desktop mode",
        "--xpra-xwayland:    Like --xpra (allows --gpu)",
        "--weston-xwayland:  Like --xephyr (allows --gpu)",
        "--hostdisplay:      Share host X display '$DISPLAY' (Less secure!)",
        "--xorg:             Xorg on tty (switch with <CTRL><ALT><F1>...<F12>)",
        "--xwayland:         Blanc Xwayland (needs running Wayland on host).",
        "--xdummy:           Invisible X server Xdummy",
        "--xvfb:             Invisible X server Xvfb",
        "--kwin-xwayland:    Like --weston-xwayland, but using kwin_wayland",
        "--kwin:             Wayland in KWin without X",
        "--weston:           Wayland in Weston without X",
        "--hostwayland:      Share host Wayland display without X",
        "--xwin:             X in Cygwin/X on MS Windows",
        "--tty:              TTY only"
      );
    gpu                   "--gpu: Hardware acceleration for OpenGL graphics."                      -> "--gpu "              | ! "" ;
    wm                    "--wm: Provide a window manager."                                        -> "--wm "               | ! "" ;
    desktop               "--desktop: Image contains a desktop environment."                       -> "--desktop "          | ! "" ;
    wayland               "--wayland: Provide a Wayland environment."                              -> "--wayland "          | ! "" ;
    fullscreen            "--fullscreen: Run X server in fullscreen mode."                         -> "--fullscreen "       | ! "" ;
    size:beside           "--size: Virtual screen size:"                                           -> "--size \""           @combow("320x240","640x480","800x600","1024x768","1280x800","1280x1024","1400x1050","1680x1200","1920x1080")="800x600"  "\" "| !"" ;
        
    xserver_appearance " Appearance of new X server"                -> scale rotate dpi outputcount keymap border xfishtank;
      scale:beside        "--scale: Zoom factor:"                                                  -> "--scale=\""          @combow("0.25","0.5","0.75","0.9","1","1.25","1.5","2","2.5","3")="1.5"                    "\" "  | ! "" ;
      rotate:beside       "--rotate: Rotation and mirroring:"                                      -> "--rotate=\""         @combo("0","90","180","270","flipped","flipped-90","flipped-180","flipped-270")="180" "\" "  | ! "" ;
      dpi:beside          "--dpi: Dots per inch (influences font size):"                           -> "--dpi=\""            @combow("36","48","60","72","84","96","120","150","256","300")="96"                   "\" "  | ! "" ;
      outputcount:beside  "--output-count: Multiple displays:"                                     -> "--output-count=\""   @combow("1","2","3","4")="2"                                                          "\" "  | ! "" ;
      keymap:beside       "--keymap: XKB keyboard layout:"                                         -> "--keymap \""         @combow('$Keymapscombo') "\" " | ! "";
      border:beside       "--border: Draw colored border in xpra windows:"                         -> "--border=\""         @combow("blue,1","yellow,2","green,1","red,3") "\" " | ! "" ;
      xfishtank           "--xfishtank: Show a fish tank."                                         -> "--xfishtank "        | ! "" ;
        
        
  options_tabbed:tabbed                         -> tab_regular tab_advanced tab_terminal;
    tab_regular:framed    "Regular Options"         -> options_normal options_hostfolders;
    tab_advanced          "Advanced Options"        -> options_advanced_framed  options_init_framed develdialog;
      options_advanced_framed:framed                    -> options_advanced;
      options_init_framed:framed                        -> options_init;
    tab_terminal:framed     "Terminal"              -> options_verbosity options_stdstream  options_show;
        
    options_normal "Internet, sound, clipboard, webcam, printer, language"    -> network clipboard pulseaudio alsa lang webcam printer;
      network:beside      "--network: Internet access (defaults to none in future)"                -> "--network=\""        @combow("","none","host")=""  "\" " | ! "";
      clipboard           "--clipboard: Clipboard sharing. (Picture clips: --xpra or --hostdisplay) \n
                           (This option also removes security restrictions of --hostdisplay)."     -> "--clipboard "        | ! "";
      pulseaudio:beside   "--pulseaudio: Sound with pulseaudio."                                   -> "--pulseaudio=\""     @combo("","socket","tcp") "\" "  | ! "";
      alsa:beside         "--alsa: Sound with ALSA. Sound card:"                                   -> "--alsa=\""           @combow('$Alsalist')="'$ALSA_CARD'" "\" "   | ! "" ;
      lang:beside         "--lang: Language locale. (Generated if missing):"                       -> "--lang=\""           @combow('$Langcombo')="'$Hostlangland'" "\" " | ! "" ;
      printer:beside      "--printer: Share printer through CUPS."                                 -> "--printer=\""        @combo("","socket","tcp") "\" "  | ! "";
      webcam              "--webcam: Share webcam device files."                                   -> "--webcam "           | ! "" ;
      
    options_hostfolders "Share host files or Docker volumes" -> home sharedir1 sharedir2;
      home                "--home: Share a host folder in ~/x11docker as HOME in container. \n
                           You can specify another folder or a Docker volume:"                     -> "--home=\""           @directory=""  "\" " | ! "";  
      sharedir1            "--share: Share a host file, folder or device, or a Docker volume:"     -> "--share=\""          @directory=""  "\" " | ! "" ;
      sharedir2            "--share: Share a host file, folder or device, or a Docker volume:"     -> "--share=\""          @directory=""  "\" " | ! "" ;

    options_advanced "Advanced options"                             -> sudouser noentrypoint shell workdir env1 env2 preset;
      sudouser            "--sudouser: Allow su and sudo in container. Password: x11docker\n
                          Severe reduction of container security!"                                 -> "--sudouser "         | ! "" ;
      noentrypoint        "--no-entrypoint: Disable ENTRYPOINT in image."                          -> "--no-entrypoint "    | ! "" ;
      shell:beside        "--shell: Set preferred shell"                                           -> "--shell=\""          @combow("bash","dash","fish","ksh","tcsh","sh","zsh")="bash" "\" " | ! "" ;
      workdir:beside      "--workdir: Working directory: "                                         -> "--workdir=\""        @string=""      "\" " | ! "";
      env1:beside         "--env: Environment variable: "                                          -> "--env=\""            @string="SHELL=/bin/sh" "\" " | ! "" ;
      env2:beside         "--env: Environment variable: "                                          -> "--env=\""            @string="" "\" " | ! "" ;
      preset:beside       "--preset: User file with predefined options:"                           -> "--preset=\""         @combo('$Presetcombo') "\" " | ! "" ;
      
    options_init "Init system and DBus in container"                -> dbus hostdbus initsystem sharecgroup;
      initsystem:beside   "--init: Run init system in container. "                                 -> "--init=" @combo( "tini","systemd","sysvinit","runit","openrc","s6-overlay","none")="systemd" " "  | ! "" ;
      dbus                "--dbus: Run DBus user session in container."                            -> "--dbus "             | ! "" ;
      hostdbus            "--hostdbus: Connect to DBus user session on host."                      -> "--hostdbus "         | ! "" ;
      sharecgroup         "--sharecgroup: Share /sys/fs/cgroup. Allows elogind in container\n
                           if used with one of --init=openrc|runit|sysvinit"                       -> "--sharecgroup "      | ! "" ;
      
    options_verbosity "Verbosity"                                   -> verbose quiet debug;
      verbose             "--verbose: Be verbose."                                                 -> "--verbose "          | ! "" ;
      quiet               "--quiet: Do not show x11docker warnings and notes."                     -> "--quiet "            | ! "" ;
      debug               "--debug: Debugging mode. Few verbose notes."                            -> "--debug "            | ! "" ;

    options_stdstream "Interaction"                                 -> interactive stdin pull pw;
      interactive         "--interactive: Run with an interactive TTY."                            -> "--interactive"       | ! "" ;
      stdin               "--stdin: Forward stdin to container stdin."                             -> "--stdin "            | ! "" ;
      pull:beside         "--pull: Allow or enforce \"docker pull\""                               -> "--pull="             @combo("ask","yes","no","always")="yes" " " | ! "" ;
      pw:beside           "--pw: Password prompt frontend:"                                        -> "--pw=\""             @combo("","su","sudo","gksu","gksudo","lxsu","lxsudo","kdesu","kdesudo","beesu","pkexec","none")="" "\" " | ! "" ;
        
    options_show "Show internals on stdout"                         -> show_explanation showenv showid showinfofile showpid1;
      show_explanation -> @text="Catch output of following options with \"read var < <(x11docker ...)\"" ;
      showenv             "--showenv: Output of environment variables of new X server."            -> "--showenv "          | ! "" ;
      showid              "--showid: Output of container ID."                                      -> "--showid "           | ! "" ;
      showinfofile        "--showinfofile: Output of path to parseable info storage file."         -> "--showinfofile "     | ! "" ;
      showpid1            "--showpid1: Output of host PID of container PID 1."                     -> "--showpid1"          | ! "" ;
                   
  develdialog:dialog "Additional special options"   -> developeroptions @execclose("echo develclose")=" close";
    developeroptions:beside                         -> develleft develright;
      develleft:framed                                      -> devauth devxconfig  devaddcommands ;
      develright:framed                                     -> devstorage devuser devcaps devrun devmisc ;
    devuser "User settings"                                         -> user hostuser groupadd;
      user:beside         "--user: Container user (name or uid): \n
                           (A gid can be specified with user:gid) \n
                           (RETAIN keeps USER from image)"                                         -> "--user=\""           @string="RETAIN" "\" " | ! "" ;
      hostuser:beside     "--hostuser: run x11docker as user \n
                           different from \$(logname)=='$Containeruser':"                          -> "--hostuser=\""       @string=""       "\" " | ! "" ;
      groupadd:beside     "--group-add: Additional groups for container user: "                    -> "--group-add=\""      @string=""       "\" " | ! "" ;
    devstorage  "Storage folders"                                   -> homebasedir cachebasedir ;
      homebasedir:beside  "--homebasedir: Host storage folder for --home: "                        -> "--homebasedir=\""    @directory=""    "\" " | ! "" ;
      cachebasedir:beside "--cachebasedir: Host cache base folder: "                               -> "--cachebasedir=\""   @directory=""    "\" " | ! "" ;
    devaddcommands "Additional commands"                            ->  runfromhost runasuser runasroot ;
      runfromhost         "--runfromhost: Run host command on new X server. Use sh syntax:"        -> "--runfromhost=\""    @string="/usr/bin/launchy &"              "\" " | ! "" ;
      runasuser           "--runasuser: Run additional command as user in container. Use sh syntax:" -> "--runasuser=\""      @string=""       "\" " | ! "" ;
      runasroot           "--runasroot: Run command as root in container. Use sh syntax:"          -> "--runasroot=\""      @string=""       "\" " | ! "" ;
    devauth "X authentication"                                      -> noauth xhost cleanxhost; 
      xhost               "--xhost: Custom xhost setting on new X server. See \"man xhost\":"      -> "--xhost=\""          @string="+SI:localuser:'$Containeruser'"  "\" " | ! "" ;
      noauth              "--no-auth: Disable access restrictions of new X server. Use with care." -> "--no-auth "          | ! "" ;
      cleanxhost          "--clean-xhost: Disable xhost access policies on host X server."         -> "--clean-xhost "      | ! "" ;
    devxconfig "X and Wayland configuration"                        -> vt display xoverip iglx composite xtest westonini;
      vt :beside          "--vt: virtual terminal/tty to use: (--xorg only)"                       -> "--vt=\""             @string="8"      "\" " | ! "";
      display :beside     "--display: display number to use (X and Wayland): "                     -> "--display=\""        @string="1000"   "\" " | ! "";
      xoverip             "--xoverip: Connect to X over TCP/IP. (Not all X servers support this.)" -> "--xoverip "          | ! "" ;
      iglx                "--iglx: Indirect rendering. Currently works with NVIDIA only."          -> "--iglx "             | ! "" ;
      composite:beside    "--composite: Enable or disable X extension COMPOSITE. \n 
                           Can fix or cause issues in nxagent."                                    -> "--composite=\""      @combo("yes","no")="yes"  "\" " | ! "";
      xtest:beside        "--xtest: Enable or disable X extension XTEST. \n 
                           Can be needed for custom xpra access."                                  -> "--xtest=\""          @combo("yes","no")="yes"  "\" " | ! "";
      westonini           "--westonini: Custom weston.ini (see man weston.ini)"                    -> "--westonini=\""      @infile="/usr/share/doc/weston/examples/weston.ini"  "\" " | ! "";
    devrun                                                          -> setcapsdialog dockeroptions;
      dockeroptions      "Custom options for \"docker run\" "                                      -> "--OPT="              @string="" ;
    devcaps "Container capabilities and namespaces, runtime"        -> name limit runtime @text("The following options severely degrade container isolation!") capdefault ipc newprivileges;
      name:beside         "--name: Container name: "                                               -> "--name=\""           @string=""       "\" " | ! "" ;
      limit:beside        "--limit: Restrict CPU and RAM usage to proportional factor"             -> "--limit=\""          @combow("0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1.0")="0.5"  "\" " | ! "";
      runtime:beside      "--runtime: Choose container runtime:"                                   -> "--runtime=\""        @combow("","runc","crun","kata-runtime","nvidia")="nvidia" "\" " | ! "";
      capdefault          "--cap-default: Allow default container capabilities."                   -> "--cap-default "      | ! "" ;
      ipc                 "--hostipc: Disable IPC namespacing. Allows MIT-SHM extension. \n
                           Shares host interprocess communication and shared memory. "             -> "--hostipc "         | ! "" ;
      newprivileges:beside  "--newprivileges: Allow process to gain more capabilities:"            -> "--newprivileges=\"" @combo("yes","no")="yes"  "\" " | ! "";
    devmisc "Miscellaneous" -> fallback nosetup enforcei;
      fallback:beside     "--fallback: Allow (default) or deny fallbacks:"                         -> "--fallback=\""      @combo("yes","no")="no"  "\" " | ! "";
      nosetup             "--no-setup: No setup inside of container. Restricts features."          -> "--no-setup "        | ! "" ;
      enforcei            "--enforce-i: Force bash to run in interactive mode."                    -> "--enforce-i "       | ! "" ;
        
  setcapsdialog:beside "Add container capabilities:"           -> capsdialog ;
    capsdialog:dialog  "docker capabilities and privileges"  -> capsreference capstable @close="close";
      capsreference " Add container capabilities degrading container isolation if needed. Use with care.\n\n
                      x11docker disables all this capabilities per default with \"--cap-drop=ALL --security-opt=no-new-privileges\". \n\n
                      Left column (SETPCAP till SETFCAP) contains capabilities normally enabled by docker per default.\n
                      Middle and right column (SYS_MODULE till BLOCK_SUSPEND) contain capabilities disabled by docker per default.\n\n
                      See \"man capabilities\" and docker run reference: https://docs.docker.com/engine/reference/run/ " -> "" ;
                       
    capstable:framed:beside -> capsleft capsmiddle capsright;
      capsleft -> drop_1 drop_2 drop_3 drop_4 drop_5 drop_6 drop_7 drop_8 drop_9 drop_10 drop_11 drop_12 drop_13 drop_14 ;
        drop_1:beside  "--cap-add"  -> "--cap-add="   @string="SETPCAP"           " " | ! "" ;
        drop_2:beside  "--cap-add"  -> "--cap-add="   @string="MKNOD"             " " | ! "" ;
        drop_3:beside  "--cap-add"  -> "--cap-add="   @string="AUDIT_WRITE"       " " | ! "" ;
        drop_4:beside  "--cap-add"  -> "--cap-add="   @string="CHOWN"             " " | ! "" ;
        drop_5:beside  "--cap-add"  -> "--cap-add="   @string="NET_RAW"           " " | ! "" ;
        drop_6:beside  "--cap-add"  -> "--cap-add="   @string="DAC_OVERRIDE"      " " | ! "" ;
        drop_7:beside  "--cap-add"  -> "--cap-add="   @string="FOWNER"            " " | ! "" ;
        drop_8:beside  "--cap-add"  -> "--cap-add="   @string="FSETID"            " " | ! "" ;
        drop_9:beside  "--cap-add"  -> "--cap-add="   @string="KILL"              " " | ! "" ;
        drop_10:beside "--cap-add"  -> "--cap-add="   @string="SETGID"            " " | ! "" ;
        drop_11:beside "--cap-add"  -> "--cap-add="   @string="SETUID"            " " | ! "" ;
        drop_12:beside "--cap-add"  -> "--cap-add="   @string="NET_BIND_SERVICE"  " " | ! "" ;
        drop_13:beside "--cap-add"  -> "--cap-add="   @string="SYS_CHROOT"        " " | ! "" ;
        drop_14:beside "--cap-add"  -> "--cap-add="   @string="SETFCAP"           " " | ! "" ;
      capsmiddle -> add_1 add_2 add_3 add_4 add_5 add_6 add_7 add_8 add_9 add_10 add_11 add_12 add_13 add_14 ;
        add_1:beside   "--cap-add"  -> "--cap-add="   @string="SYS_MODULE"        " " | ! "" ;
        add_2:beside   "--cap-add"  -> "--cap-add="   @string="SYS_RAWIO"         " " | ! "" ;
        add_3:beside   "--cap-add"  -> "--cap-add="   @string="SYS_PACCT"         " " | ! "" ;
        add_4:beside   "--cap-add"  -> "--cap-add="   @string="SYS_ADMIN"         " " | ! "" ;
        add_5:beside   "--cap-add"  -> "--cap-add="   @string="SYS_NICE"          " " | ! "" ;
        add_6:beside   "--cap-add"  -> "--cap-add="   @string="SYS_RESOURCE"      " " | ! "" ;
        add_7:beside   "--cap-add"  -> "--cap-add="   @string="SYS_TIME"          " " | ! "" ;
        add_8:beside   "--cap-add"  -> "--cap-add="   @string="SYS_TTY_CONFIG"    " " | ! "" ;
        add_9:beside   "--cap-add"  -> "--cap-add="   @string="AUDIT_CONTROL"     " " | ! "" ;
        add_10:beside  "--cap-add"  -> "--cap-add="   @string="MAC_OVERRIDE"      " " | ! "" ;
        add_11:beside  "--cap-add"  -> "--cap-add="   @string="MAC_ADMIN"         " " | ! "" ;
        add_12:beside  "--cap-add"  -> "--cap-add="   @string="NET_ADMIN"         " " | ! "" ;
        add_13:beside  "--cap-add"  -> "--cap-add="   @string="SYSLOG"            " " | ! "" ;
        add_14:beside  "--cap-add"  -> "--cap-add="   @string="DAC_READ_SEARCH"   " " | ! "" ;
      capsright -> add_15 add_16 add_17 add_18 add_19 add_20 add_21 add_22 add_23 add_24 @fill device_1 device_2 device_3 @fill privileged;
        add_15:beside  "--cap-add"  -> "--cap-add="   @string="LINUX_IMMUTABLE"   " " | ! "" ;
        add_16:beside  "--cap-add"  -> "--cap-add="   @string="NET_BROADCAST"     " " | ! "" ;
        add_17:beside  "--cap-add"  -> "--cap-add="   @string="IPC_LOCK"          " " | ! "" ;
        add_18:beside  "--cap-add"  -> "--cap-add="   @string="IPC_OWNER"         " " | ! "" ;
        add_19:beside  "--cap-add"  -> "--cap-add="   @string="SYS_PTRACE"        " " | ! "" ;
        add_20:beside  "--cap-add"  -> "--cap-add="   @string="SYS_BOOT"          " " | ! "" ;
        add_21:beside  "--cap-add"  -> "--cap-add="   @string="LEASE"             " " | ! "" ;
        add_22:beside  "--cap-add"  -> "--cap-add="   @string="WAKE_ALARM"        " " | ! "" ;
        add_23:beside  "--cap-add"  -> "--cap-add="   @string="BLOCK_SUSPEND"     " " | ! "" ;
        add_24:beside  "--cap-add"  -> "--cap-add="   @string="AUDIT_READ"        " " | ! "" ;
        device_1:beside"--device"   -> "--device="    @string=""                  " " | ! "" ;
        device_2:beside"--device"   -> "--device="    @string=""                  " " | ! "" ;
        device_3:beside"--device"   -> "--device="    @string=""                  " " | ! "" ;
        privileged     "--privileged" -> "--privileged "                              | ! "" ;

                 
previewline:beside -> previewclipboard previewtext;
previewtext        -> @text="";
previewclipboard   -> @echo("--CLIPBOARD")="Copy to clipboard:" ;

buttons:horizontal ->  run runxterm starter password cleanupxterm quit;
  run              -> @echo("--RUN")=" Run";
  starter          -> @echo("--STARTER")="Create starter on desktop" ;
  runxterm         -> @echo("--XTERM")="Run in xterm" ;
  password         -> @echo("--PWD")="Change password" ;
  cleanupxterm     -> @echo("--CLEANUP")="Clean up and exit";
  quit             -> @dump("--EXIT")="Exit" ;
'

# remove double whitespaces
Kaptain_grammar="$(sed -e 's/  */ /g' <<< "$Kaptain_grammar")"
echo "$Kaptain_grammar" >> $Kaptain_grammar_file

# output of kaptain grammar script
verbose "created kaptain grammar:
$(printf "%s" "$Kaptain_grammar" | nl)
"

# check if kaptain is available on host
command -v kaptain >/dev/null && Hostkaptain="yes" || Hostkaptain="no"

# check kaptain version
verbose "$(kaptain --version 2>/dev/null)"
[ "$Hostkaptain" = "yes" ] && kaptain --version 2>&1 | grep -q 'kaptain 0.72' && {
  # regard kde3-kaptain from openSUSE KDE:KDE3 repository
  # up to date from decades ago is 0.73
  warning " Your version 0.72 of kaptain is outdated.
  kaptain version 0.73 with important bugfixes is available at:
    https://github.com/mviereck/kaptain
  Output of 'kaptain --version':
$(kaptain --version)
  Fallback: Using image x11docker/kaptain instead"
  Hostkaptain="no"
}

# Run kaptain either from host or with x11docker/kaptain image
case $Hostkaptain in
  yes)
    Exe="--exe"
    Cmd=kaptain
  ;;
  no)
    Exe=
    Cmd=x11docker/kaptain
    warning "Did not find executeable 'kaptain'.
If your distribution does not provide package kaptain (>=0.73), look at
kaptain repository:   https://github.com/mviereck/kaptain
Fallback: Will try to use image x11docker/kaptain."
  ;;
esac
[ "$X11docker_icon" ] && X11docker_icon="--share $X11docker_icon"

{ IFS= read X11DOCKER_CACHE ; read Kaptain_pid ; } < <($X11docker_exe $Verboseoption \
                                                           --pull=ask \
                                                           --stdin --showcache --showpid1 \
                                                           --clipboard --hostdisplay \
                                                           $X11docker_icon \
                                                           --share "$Kaptain_grammar_file" \
                                                           --share "$Containeruserhome:ro" \
                                                           --env HOME="$Containeruserhome" \
                                                           $Exe \
                                                           -- \
                                                      $Cmd --stdio $Kaptain_grammar_file)
                                                      
[ "$Kaptain_pid" ] && {
  Kaptain_stdin="$X11DOCKER_CACHE/share/stdin"
  exec 6<>$Kaptain_stdin
  Kaptain_stdout="$X11DOCKER_CACHE/share/stdout"
  Timetosaygoodbye="$X11DOCKER_CACHE/share/timetosaygoodbye.fifo"
  [ "$DISPLAY$WAYLAND_DISPLAY" ] || export $Xenv
} || {
  warning "Startup of $Cmd was not successfull.
  Please install package kaptain or provide image x11docker/kaptain
  with:   docker pull x11docker/kaptain"
  exit 1
}

send() {
  # send messages to kaptain over its stdin
  echo "$1" >&6
  verbose "sent: $1"
}

# watch kaptain messages
while read Line; do
  verbose "received: $Line"
  
  # Option output starts with --
  # --CAPitalized options are internal x11docker-gui signals
  [ "$(echo $Line | cut -c1-2)" = "--" ] && {
    case "$(echo $Line | cut -c1-5)" in
      
      --OPT) # custom docker options appear behind -- and without on x11docker option on their own
        Line="$(echo "$Line" | cut -c7-)"
        Previewline="$Previewline $Line -- "
        Line=""
      ;;
      
      --IMG) # docker image and command. Appears after -- without an opion on its own. Last one in refresh request sequence. 
        Line="$(echo "$Line" | cut -c7-)"
        [ "$Line" = '""' ] || Previewline="$Previewline $Line"
        Line=""
        
        # refresh request is ready, write preview line into GUI
        send "previewtext='$Previewline'"
        
        # execute x1docker if "Run" button was pressed before
        [ "$Runit" ] && {
          verbose "Running: $Previewline"
          case $Runit in
            run)
              bash -c "$Previewline" & Pid=$!
            ;;
            runxterm)
              xterm -hold -e "$Previewline" & Pid=$!
            ;;
            runstarter)
              xterm -e "$X11docker_exe --starter $(echo "$Previewline" | cut -d' ' -f2-)" & Pid=$! 
            ;;
          esac
          disown $Pid
          { # show pstree of x11docker
            sleep 5
            verbose "pstree of: $Previewline
$(pstree -p $Pid)"
          } &
          Previewline="$X11docker_exe" # workaround for double-receiving options that happens for unknown reason if editing image+command line and pressing "Run"
          Runit=""
        }
      ;;
      
      --RUN) # "Run" button was pressed
        { # animated run button to give some startup feedback
          for Count in 1 2 3 4 5; do
            send "run('--RUN')='Starting x11docker *'"
            sleep 0.5
            send "run('--RUN')='* Starting x11docker'"
            sleep 0.5
          done 
          send "run('--RUN')=' Run'"
        } &
        # x11docker will run after refresh in --IMG)
        Runit="run"
        # call for rehfresh of option preview
        Line="refresh"
      ;;
      
      --XTE) # --XTERM: run in xterm window
        Runit="runxterm"
        Line="refresh"
        command -v xterm >/dev/null || send "runxterm='Please install xterm'"
      ;;
      
      --CLE) # --CLEANUP: run --cleanup in xterm window
        command -v xterm >/dev/null && {
          send "run('--RUN')='(disabled, cleaning up now)'"
          env X11DOCKER_LASTCLEANFOLDER="$(basename $X11DOCKER_CACHE)" xterm -hold -e "$X11docker_exe --cleanup"
          finish
        } || send "runxterm='Please install xterm'"
      ;;
      
      --PWD) # --PWD: run --password in xterm window
        command -v xterm >/dev/null && {
          xterm -hold -e "$X11docker_exe --password"
          :
        } || send "runxterm='Please install xterm'"
      ;;
      
      --STA) # --STARTER: create desktop starter icon
        Runit="runstarter"
        Line="refresh"
        command -v xterm >/dev/null || send "runxterm='Please install xterm'"
      ;;
      
      --CLI)  # --CLIPBOARD: copy to clipboard
        command -v xclip >/dev/null && {
          echo -n "$Previewline" | xclip -selection clipboard
        } || {
          echo -n "$Previewline" | xsel -i -b
        } || send "previewclipboard='Please install xsel or xclip'"
      ;;
      
      --EXI)  # --EXIT: Button "Quit" or tail exits due to closed x1ddocker-gui window
        break 
      ;;
      
      *) # other messages beginning with -- are chosen options.
        Previewline="$Previewline $Line"
        Line=""
      ;;
    esac
  }
  
  # on all action messages (without --) refresh preview line. Ask for all set options to be written on stdout.
  [ "$Line" ] && {
    verbose "Refreshing preview"
    Previewline="$X11docker_exe"
    for Line in backend xserver_choose xserver_appearance \
                options_normal options_hostfolders \
                options_advanced options_init \
                options_verbosity options_stdstream options_show \
                devaddcommands devauth devxconfig devmisc devuser devcaps \
                imageorexe capstable dockeroptions dockerimage; do
      send "$Line?"
    done
  }
done < <(tail -f ${Kaptain_stdout:-"filenotfound"} --pid ${Kaptain_pid:-"-1"} 2>/dev/null; echo '--EXIT')
